package xyz.xtt.asset.service.impl;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.tongtong.middleware.vo.params.AccountParams;
import com.tongtong.middleware.vo.response.AccountResponse;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.asset.api.enums.AssetTypeEnum;
import xyz.xtt.asset.api.enums.OwnerTypeEnum;
import xyz.xtt.asset.api.enums.SellPatternEnum;
import xyz.xtt.asset.api.enums.ShowStateEnum;
import xyz.xtt.asset.api.enums.StatusAllEnum;
import xyz.xtt.asset.client.request.AssetNoInfoRequest;
import xyz.xtt.asset.client.response.AssetAccountResponse;
import xyz.xtt.asset.client.response.AssetAllInfoResponse;
import xyz.xtt.asset.client.response.AssetDetailInfo;
import xyz.xtt.asset.client.response.AssetExtInfoResponse;
import xyz.xtt.asset.client.response.AssetInfoResponse;
import xyz.xtt.asset.client.response.AssetStaticResponse;
import xyz.xtt.asset.client.response.AssetVo;
import xyz.xtt.asset.client.response.NftListVo;
import xyz.xtt.asset.client.response.SecondAssetListVo;
import xyz.xtt.asset.client.response.TblAssetExpandDto;
import xyz.xtt.asset.client.response.TblAssetExtDto;
import xyz.xtt.asset.client.response.TblAssetUserLogDto;
import xyz.xtt.asset.constants.AssetCache;
import xyz.xtt.asset.constants.ErrorMessage;
import xyz.xtt.asset.entity.TblAsset;
import xyz.xtt.asset.entity.TblAssetAccount;
import xyz.xtt.asset.entity.TblAssetExt;
import xyz.xtt.asset.entity.TblAssetGroup;
import xyz.xtt.asset.entity.TblAssetNo;
import xyz.xtt.asset.entity.TblAssetStaticResource;
import xyz.xtt.asset.entity.TblAssetTransaction;
import xyz.xtt.asset.entity.TblAssetUserLog;
import xyz.xtt.asset.enums.CertificateTypeEnum;
import xyz.xtt.asset.enums.FileTypeEnum;
import xyz.xtt.asset.enums.SortOrder;
import xyz.xtt.asset.enums.SortType;
import xyz.xtt.asset.exception.ChainException;
import xyz.xtt.asset.mapper.TblAssetMapper;
import xyz.xtt.asset.request.CreateRequest;
import xyz.xtt.asset.request.UpdateAssertRequest;
import xyz.xtt.asset.response.QueryAssetInfoResponse;
import xyz.xtt.asset.service.IInnerService;
import xyz.xtt.asset.service.INftService;
import xyz.xtt.asset.service.ITblAssetAccountService;
import xyz.xtt.asset.service.ITblAssetExtService;
import xyz.xtt.asset.service.ITblAssetGroupService;
import xyz.xtt.asset.service.ITblAssetNoService;
import xyz.xtt.asset.service.ITblAssetService;
import xyz.xtt.asset.service.ITblAssetStaticResourceService;
import xyz.xtt.asset.service.ITblAssetTransactionService;
import xyz.xtt.asset.service.ITblAssetUserLogService;
import xyz.xtt.asset.service.IThirdService;
import xyz.xtt.asset.utils.RedisLockUtil;
import xyz.xtt.asset.vo.AssetBaseInfo;
import xyz.xtt.asset.vo.AssetSynMessageSaveItem;
import xyz.xtt.asset.vo.AssetTypeItem;
import xyz.xtt.asset.vo.AssetTypeSubIPagetem;
import xyz.xtt.asset.vo.AssetTypeSubItem;
import xyz.xtt.asset.vo.MyAssetsResponse;
import xyz.xtt.asset.vo.MyAssetsVo;
import xyz.xtt.asset.vo.PaySyncMessageVo;
import xyz.xtt.asset.vo.SyncResponseMsg;
import xyz.xtt.common.objects.CopyUtils;
import xyz.xtt.common.starter.cache.CacheManager;
import xyz.xtt.user.client.response.UserInfoResponse;

/**
 * <p>
 * 资产基础信息表 服务实现类
 * </p>
 *
 * @author author
 * @since 2024-10-17
 */
@RefreshScope
@Slf4j
@Service
public class TblAssetServiceImpl extends ServiceImpl<TblAssetMapper, TblAsset> implements ITblAssetService {
	private static final String ASSET_CONTENT = "通通元素系列数字藏品";
	private static final int BATCH_SIZE = 100;
	@Autowired
	private ITblAssetExtService iTblAssetExtService;
	@Autowired
	private ITblAssetStaticResourceService iTblAssetStaticResourceService;
	@Autowired
	private ITblAssetTransactionService iTblAssetTransactionService;
	@Autowired
	private ITblAssetAccountService iTblAssetAccountService;
	@Autowired
	private IInnerService iInnerService;
	@Autowired
	private IThirdService thirdService;
	@Autowired
	private CacheManager cacheManager;
	@Autowired
	private RedisLockUtil redisLockUtil;
	@Autowired
	private INftService nftService;
	@Autowired
	private ITblAssetAccountService tblAssetAccountService;
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	private Environment environment;
	@Autowired
	private ITblAssetGroupService iTblAssetGroupService;
	@Autowired
	private ITblAssetNoService iTblAssetNoService;
	@Value("${personalUrl.sendUrl}")
	String sendUrl;
	@Value("${personalUrl.receiveUrl}")
	String receiveUrl;
	/** 更多 */
	@Value("${personalUrl.more.url}")
	private String moreUrl;
	/** 地皮详情 */
	@Value("${personalUrl.land.detail}")
	private String landDetailUrl;
	@Value("${personalUrl.nft.detail}")
	private String nftDetailUrl;
	@Value("${personalUrl.nft.more}")
	private String nftMoreUrl;
	/**
	 * 用户数藏列表开关
	 */
	@Value("${personalUrl.nft.switch:true}")
	private Boolean nftSwitch;
	/**
	 * nft是否展示
	 */
	@Value("${personalUrl.nft.show:true}")
	private Boolean nftShow;
	/** 经营证详情 */
	@Value("${personalUrl.business.detail}")
	private String businessDetail;
	@Value("${asset.type.pagesize:10}")
	private Long assetTypePageSize;
	@Value("${asset.sync.pagesize:100}")
	private Integer assetSyncePageSize;

	@Value("${asset.rose.fakeStart}")
	private String fakeStart;

	@Value("${asset.page.size:10}")
	private Long pageSize;

	@Value("${asset.mint.secondTypeShelfTimeMinutes:5}")
	private Integer secondTypeShelfTimeMinutes;

	@Autowired
	private ITblAssetUserLogService iTblAssetUserLogService;

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean createBatch(List<CreateRequest> createRequestList) {
		List<List<CreateRequest>> subList = Lists.partition(createRequestList, BATCH_SIZE);
		for (List<CreateRequest> list : subList) {
			for (CreateRequest request : list) {
				// create(request);
			}
		}
		return true;
	}

	/**
	 * 
	 * 3种可能的结果
	 * 
	 * 1 经营证和地皮全开 经营证和地皮上都有ownerId
	 * 
	 * 2 经营证开，地皮不开 经营证上有ownerId 地皮上没有ownerId
	 * 
	 * 3 只开地皮 此时没有经营证的信息
	 * 
	 * (non-Javadoc)
	 * 
	 * @see xyz.xtt.pay.service.ITblAssetService#create(xyz.xtt.pay.request.CreateRequest)
	 */
//	@Override
//	public Boolean create(CreateRequest createRequest) {
//		String busiId = createRequest.getBusiId();
//		TblAsset info = info(busiId);
//		if (Objects.nonNull(info)) {
//			log.info("busiId: {} 对应的资产信息已存地", busiId);
//			return false;
//		}
//		String typeCode = createRequest.getTypeCode();
//		AssetTypeEnum firstType = AssetTypeEnum.convert(typeCode);
//		if (Objects.isNull(firstType)) {
//			log.info("typeCode: {} 不是有效的 资产类型", typeCode);
//			return false;
//		}
//		ReferProtocolEnum tokenType = ReferProtocolEnum.convert(createRequest.getTokenType());
//		if (Objects.isNull(tokenType)) {
//			log.info("tokenType: {} 不是有效的 ReferProtocolEnum 类型", createRequest.getTokenType());
//			return false;
//		}
//
//		String metaDataStr = createRequest.getMetaData();
//		boolean json = JSONUtil.isTypeJSON(metaDataStr);
//		if (!json) {
//			log.info("metaDataStr: {} 不是有效的 json 数据格式", metaDataStr);
//			return false;
//		}
//		long id = IdUtil.getSnowflakeNextId();
//		// 地皮
//		if (firstType == AssetTypeEnum.GROUND) {
//			GroundMetaData metaData = JSONUtil.toBean(metaDataStr, GroundMetaData.class);
//			if (Objects.isNull(metaData)) {
//				log.info("GroundMetaData metaData convert json is null: {} 不是有效的 json 数据格式", metaDataStr);
//				return false;
//			}
//			GroundTypeEnum secondType = GroundTypeEnum.convert(metaData.getType());
//			if (Objects.isNull(secondType)) {
//				return false;
//			}
//			// 有可能先把经营证，后发地皮
//			// 资产主属性
//			TblAsset groundAsset = createRequest
//					.buildGroundAsset(id, Longs.tryParse(busiId), tokenType, firstType.code(), secondType.code(), metaData.getDesc());
//			// 资产扩展属性
//			TblAssetExt groundAssetExt = createRequest.buildGroundAssetExt(id, tokenType);
//
//			TblAssetStaticResource staticResource = buildStaticResource(id, createRequest.file(), createRequest.file(), FileTypeEnum.IMG);
//			// 地皮可能不入库，把地皮的metadata信息放到经营证上
//			// A类经营证 入库
//			flatCertificateSave(groundAsset, groundAssetExt, staticResource, metaData.getFirst(), CertificateTypeEnum.A, metaData);
//			// B类经营证 入库
//			flatCertificateSave(groundAsset, groundAssetExt, staticResource, metaData.getSecond(), CertificateTypeEnum.B, metaData);
//			// 如果是商业地皮,没有拥有人, 数据不处理, 只处理经营证
//			if (secondType == GroundTypeEnum.COMMERCIAL && StringUtils.isBlank(groundAsset.getOwnerId())) {
//				return true;
//			}
//			this.save(groundAsset);
//			this.iTblAssetExtService.save(groundAssetExt);
//			this.iTblAssetStaticResourceService.save(staticResource);
//
//		} else if (firstType == AssetTypeEnum.BIZ_CERTIFICATE) {
//			CertificateMetaData bean = JSONUtil.toBean(metaDataStr, CertificateMetaData.class);
//			if (Objects.isNull(bean)) {
//				log.info("CertificateMetaData metaData convert json is null: {} 不是有效的 json 数据格式", metaDataStr);
//				return false;
//			}
//			String landId = bean.getLandId();
//			if (StringUtils.isBlank(landId)) {
//				log.info("landId is empty bean obj: {}", JSONUtil.toJsonStr(bean));
//				return false;
//			}
//			CertificateTypeEnum secondType = CertificateTypeEnum.convert(bean.getType());
//			if (Objects.isNull(secondType)) {
//				return false;
//			}
//			TblAsset groundAsset = createRequest
//					.buildGroundAsset(id, Longs.tryParse(landId), tokenType, firstType.code(), secondType.code(), StringUtils.EMPTY);
//			TblAssetExt groundAssetExt = createRequest.buildGroundAssetExt(id, tokenType);
//			TblAssetStaticResource staticResource = buildStaticResource(id, createRequest.getFileUrl(), createRequest.getFileUrl(), FileTypeEnum.IMG);
//			this.save(groundAsset);
//			this.iTblAssetExtService.save(groundAssetExt);
//			this.iTblAssetStaticResourceService.save(staticResource);
//		}
//		return true;
//	}

//	public void flatCertificateSave(TblAsset groundAsset, TblAssetExt groundAssetExt, TblAssetStaticResource staticResource, List<CertificateData> list,
//			CertificateTypeEnum type, GroundMetaData groundMetaData) {
//		List<TblAsset> assetList = Lists.newArrayList();
//		List<TblAssetExt> assetExtList = Lists.newArrayList();
//		List<TblAssetStaticResource> staticResourceList = Lists.newArrayList();
//		CerGroundMetaData ground = CerGroundMetaData
//				.builder()
//				.address(groundMetaData.getAddress())
//				.area(groundMetaData.getArea())
//				.lat(groundMetaData.getLat())
//				.lon(groundMetaData.getLon())
//				.desc(groundMetaData.getDesc())
//				.type(groundMetaData.getType())
//				.level(groundMetaData.getLevel())
//				.fileUrl(staticResource.getFileUri())
//				.name(groundAsset.getAssetName())
//				.firstTotal(groundMetaData.getFirstTotal())
//				.secondTotal(groundMetaData.getSecondTotal())
//				.build();
//		for (CertificateData x : list) {
//			if (StringUtils.isBlank(x.getOwnerId())) {
//				continue;
//			}
//			TblAsset buildCerAsset = x.buildAsset(groundAsset, type, x.getBusId());
//			CertificateMetaData build = CertificateMetaData.builder().landId(groundAsset.getGoodsId()).ground(ground).type(type.name()).build();
//			buildCerAsset.setMetaData(JSONUtil.toJsonStr(build));
//			TblAssetExt buildCerAssetExt = x.buildAssetExt(groundAssetExt, buildCerAsset.getId());
//			TblAssetStaticResource buildStaticResource = buildStaticResource(buildCerAsset.getId(), x.file(), x.file(), FileTypeEnum.IMG);
//			assetList.add(buildCerAsset);
//			assetExtList.add(buildCerAssetExt);
//			staticResourceList.add(buildStaticResource);
//		}
//		if (CollectionUtil.isNotEmpty(assetList) && CollectionUtil.isNotEmpty(assetExtList) && CollectionUtil.isNotEmpty(staticResourceList)) {
//			// 入库
//			this.saveBatch(assetList);
//			this.iTblAssetExtService.saveBatch(assetExtList);
//			this.iTblAssetStaticResourceService.saveBatch(staticResourceList);
//		}
//	}

	public TblAssetStaticResource buildStaticResource(long assetId, String logoUrl, String fileUrl, FileTypeEnum fileType) {
		return TblAssetStaticResource
				.builder()
				.id(IdUtil.getSnowflakeNextId())
				.assetId(assetId)
				.logoUri(logoUrl)
				.displayUri(fileUrl)
				.fileUri(fileUrl)
				.fileType(fileType.code())
				.lastModifyTime(System.currentTimeMillis())
				.build();
	}

	@Override
	public TblAsset info(String busId) {
		if (StringUtils.isBlank(busId)) {
			return null;
		}
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.eq(TblAsset::getGoodsId, busId).one();
	}

	@Override
	public TblAsset infoWithBizIdByCache(String busId) {
		return cacheManager.cacheObj(() -> info(busId), AssetCache.TBL_ASSET, busId);
	}

	@Override
	public TblAsset infoWithIdByCache(Long id) {
//		return cacheManager.cacheObj(() -> this.getById(id), AssetCache.TBL_ASSET_WITH_ID, String.valueOf(id));
		return this.getById(id);
	}

	@Override
	public List<TblAsset> info(List<String> busIds) {
		if (CollectionUtil.isEmpty(busIds)) {
			return null;
		}
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.notIn(TblAsset::getGoodsId, busIds).list();
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public Boolean update(UpdateAssertRequest updateAssertRequest) {
		if (Objects.isNull(updateAssertRequest)) {
			return true;
		}
		String busId = updateAssertRequest.getBusId();
		TblAsset info = info(busId);
		if (Objects.isNull(info)) {
			log.info("busId: {} 对应的地皮信息找不到", busId);
			throw new ChainException(ErrorMessage.TARGET_GROUND_NOT_FOUND);
		}
		long id = info.getId();
		Boolean updatePriceResult = Boolean.FALSE;
		Boolean updateSellTimeResult = Boolean.FALSE;
		if (updateAssertRequest.checkNewPrice()) {
			LambdaUpdateChainWrapper<TblAsset> updateWrapper = new LambdaUpdateChainWrapper<>(baseMapper);
			updateWrapper
					.eq(TblAsset::getId, id)
					.eq(TblAsset::getPrice, updateAssertRequest.getOldPrice())
					.set(TblAsset::getPrice, new BigDecimal(String.valueOf(updateAssertRequest.getNewPrice())));
			updatePriceResult = updateWrapper.update();
			log.info("updatePriceResult: {}", updatePriceResult);
		}
		if (updateAssertRequest.checkSellStart()) {
			LambdaUpdateChainWrapper<TblAssetExt> updateWrapper = new LambdaUpdateChainWrapper<>(this.iTblAssetExtService.getBaseMapper());
			updateWrapper.eq(TblAssetExt::getAssetId, id).set(TblAssetExt::getSellStartDatetime, updateAssertRequest.getSellStart());
			updateSellTimeResult = updateWrapper.update();
			log.info("updateSellTimeResult: {}", updateSellTimeResult);
		}
		// 这两个状态不一样,说明只有一个操作成功了,回退
		if (Objects.equals(updatePriceResult, Boolean.TRUE) || Objects.equals(updateSellTimeResult, Boolean.TRUE)) {
			// 清除缓存
			cacheManager.delete(AssetCache.TBL_ASSET, busId);
			return true;
		}
		return false;
	}

	private List<TblAsset> queryCertificate(String assertId) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.eq(TblAsset::getParentId, assertId).ne(TblAsset::getId, Long.valueOf(assertId)).list();
	}

	@Override
	public QueryAssetInfoResponse queryInfo(String assetsCode) {
		TblAsset info = info(assetsCode);
		if (Objects.isNull(info)) {
			return null;
		}
		long id = info.getId();
		TblAssetExt extInfo = iTblAssetExtService.info(id);
		TblAssetStaticResource staticInfo = iTblAssetStaticResourceService.info(id);
		AssetTypeEnum assetType = AssetTypeEnum.queryConvert(info.getFirstType());
		QueryAssetInfoResponse build = QueryAssetInfoResponse
				.builder()
				.tokenType(extInfo.getReferProtocol())
				.fileUrl(staticInfo.getFileUri())
				.nums(info.getAmount())
				.ownerId(info.getOwnerId())
				.unit(info.getUnit())
				.chainId(Ints.tryParse(extInfo.getChainId()))
				.price(info.getPrice().doubleValue())
				.name(info.getAssetName())
				.sellStart(extInfo.getSellEndDatetime())
				.txHash(extInfo.getChainTxHash())
				.upTime(Objects.isNull(extInfo.getChainTime()) ? 0L : extInfo.getChainTime())
				.build();
		if (assetType == AssetTypeEnum.GROUND) {
			List<TblAsset> queryCertificate = queryCertificate(info.getParentId());
			Map<Integer, Long> groupCount = queryCertificate.stream().collect(Collectors.groupingBy(TblAsset::getSecondType, Collectors.counting()));
			// a b类经营证的数量
			Long first = groupCount.getOrDefault(CertificateTypeEnum.A.code(), 0L);
			Long second = groupCount.getOrDefault(CertificateTypeEnum.B.code(), 0L);
			Map<String, Long> meta = Map.of("first", first, "second", second);
			build.setMetaData(meta);
		} else if (assetType == AssetTypeEnum.BIZ_CERTIFICATE) {
			CertificateTypeEnum queryConvert = CertificateTypeEnum.queryConvert(info.getSecondType());
			Map<String, String> meta = Map.of("landId", info.getGoodsId(), "type", queryConvert.name());
			build.setMetaData(meta);
		}
		build.setTypeCode(assetType.name());
		return build;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean payResultSync(PaySyncMessageVo paySyncMessageVo) {
		return Boolean.FALSE;
	}

	private boolean updateOwner(String buyUserId, Set<String> keySet) {
		LambdaUpdateChainWrapper<TblAsset> updateWrapper = new LambdaUpdateChainWrapper<>(this.baseMapper);
		updateWrapper.in(TblAsset::getGoodsId, keySet).set(TblAsset::getShowState, ShowStateEnum.DOWN.code()).set(TblAsset::getOwnerId, buyUserId);
		return updateWrapper.update();
	}

	private boolean updateOwnerAfterSale(String buyUserId, String goodsId) {
		LambdaUpdateChainWrapper<TblAsset> updateWrapper = new LambdaUpdateChainWrapper<>(this.baseMapper);
		updateWrapper
				.eq(TblAsset::getGoodsId, goodsId)
				.eq(TblAsset::getShowState, ShowStateEnum.NORMAL.code())
				.set(TblAsset::getShowState, ShowStateEnum.DOWN.code())
				.set(TblAsset::getOwnerId, buyUserId);
		return updateWrapper.update();
	}

	@Override
	public Boolean updateOwner(String from, String to, Long assetId) {
		LambdaUpdateChainWrapper<TblAsset> updateWrapper = new LambdaUpdateChainWrapper<>(this.baseMapper);
		updateWrapper
				.eq(TblAsset::getId, assetId)
				.eq(TblAsset::getOwnerId, from)
				.set(TblAsset::getShowState, ShowStateEnum.DOWN.code())
				.set(TblAsset::getOwnerId, to);
		return updateWrapper.update();
	}

	/**
	 * 查询商品id对应资产信息
	 *
	 * @param assetGoodsId 商品id
	 * @return 资产
	 */
	@Override
	public TblAsset assetInfoWithGoodsId(String assetGoodsId) {
		return cacheManager
				.cacheObj(() -> new LambdaQueryChainWrapper<>(baseMapper).eq(TblAsset::getGoodsId, assetGoodsId).one(), AssetCache.TBL_ASSET, assetGoodsId);
	}

	private List<TblAsset> queryAfterAdd(List<String> queryGoodsId) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.in(TblAsset::getGoodsId, queryGoodsId).list();
	}

	@Override
	public MyAssetsVo getMyAssets(String uid) {
		MyAssetsVo myAssets = new MyAssetsVo();
		UserInfoResponse userInfo = iInnerService.userInfo(Longs.tryParse(uid));
		if (Objects.isNull(userInfo)) {
			throw new ChainException(ErrorMessage.TARGET_USER_EXIST);
		}
		address(myAssets, uid);
		// 配置：通通->家->我的->接收 & 发送 按钮链接
		myAssets.setSend(sendUrl + uid);
		myAssets.setReceive(receiveUrl + myAssets.getOpbAddress());
		// 资产分类处理
//		List<AssetTypeItem> list = Lists.newArrayList();
//		AssetTypeSubIPagetem groundTypeList = queryAssetTypeEx(uid, Arrays.asList(AssetTypeEnum.GROUND.code()));
//		AssetTypeSubIPagetem bizTypeList = queryAssetTypeEx(uid, Arrays.asList(AssetTypeEnum.BIZ_CERTIFICATE.code()));
//		list.add(buildItemEx(groundTypeList, AssetTypeEnum.GROUND, moreUrl, landDetailUrl));
//		list.add(buildItemEx(bizTypeList, AssetTypeEnum.BIZ_CERTIFICATE, moreUrl, businessDetail));
//		// 默认不展示
//		if (Objects.equals(Boolean.TRUE, nftShow)) {
//			AssetTypeSubIPagetem nftList = queryAssetTypeEx(uid, AssetTypeEnum.queryNftTypes());
//			// 如果入口开放，所有人都加载
//			if (Objects.equals(Boolean.TRUE, nftSwitch)) {
//				list.add(buildItemEx(nftList, AssetTypeEnum.NFT, nftMoreUrl, nftDetailUrl));
//			} else {
//				// 如果入口不开放，有数据的人才加载
//				if (CollectionUtil.isNotEmpty(nftList.getList())) {
//					list.add(buildItemEx(nftList, AssetTypeEnum.NFT, nftMoreUrl, nftDetailUrl));
//				}
//			}
//		}
		List<AssetTypeItem> cacheList = cacheManager.cacheList(() -> myAssetlist(uid), AssetCache.QUERY_MY_ASSETS, uid);
		myAssets.setList(cacheList);
		return myAssets;
	}

	private List<AssetTypeItem> myAssetlist(String uid) {
		List<AssetTypeItem> list = Lists.newArrayList();
		AssetTypeSubIPagetem groundTypeList = queryAssetTypeEx(uid, Arrays.asList(AssetTypeEnum.GROUND.code()));
		AssetTypeSubIPagetem bizTypeList = queryAssetTypeEx(uid, Arrays.asList(AssetTypeEnum.BIZ_CERTIFICATE.code()));

		list.add(buildItemEx(groundTypeList, AssetTypeEnum.GROUND, moreUrl, landDetailUrl));
		list.add(buildItemEx(bizTypeList, AssetTypeEnum.BIZ_CERTIFICATE, moreUrl, businessDetail));
		// 默认不展示
		if (Objects.equals(Boolean.TRUE, nftShow)) {
			AssetTypeSubIPagetem nftList = queryAssetTypeEx(uid, AssetTypeEnum.queryNftTypes());
			// 如果入口开放，所有人都加载
			if (Objects.equals(Boolean.TRUE, nftSwitch)) {
				list.add(buildItemEx(nftList, AssetTypeEnum.NFT, nftMoreUrl, nftDetailUrl));
			} else {
				// 如果入口不开放，有数据的人才加载
				if (CollectionUtil.isNotEmpty(nftList.getList())) {
					list.add(buildItemEx(nftList, AssetTypeEnum.NFT, nftMoreUrl, nftDetailUrl));
				}
			}
		}
		return list;
	}

	private void address(MyAssetsVo myAssets, String uid) {
		AccountParams req = new AccountParams();
		req.setUserId(uid);
		AccountResponse account = thirdService.createAndQueryAccount(req);
		Optional.ofNullable(account).map(AccountResponse::getAddress).filter(StringUtils::isNotBlank).ifPresentOrElse(x -> {
			myAssets.setOpbAddress(account.getAddress());
		}, () -> {
			log.info("uid: {}, address is null", uid);
		});
	}

	@Override
	public List<AssetVo> queryAsset(String uid) {
		return this.baseMapper.getMyAssetsListByUid(uid);
	}

	private List<TblAsset> queryExistWithGoogIds(Collection<String> queryGoodsId) {
		if (CollectionUtil.isEmpty(queryGoodsId)) {
			return Lists.newArrayList();
		}
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.select(TblAsset::getGoodsId).in(TblAsset::getGoodsId, queryGoodsId).list();
	}

//	@Transactional(rollbackFor = Exception.class)
//	@Override
//	public List<SyncResponseMsg> parseBatch(List<CreateRequest> list) {
//		List<AssetSynMessageSaveItem> targetList = Lists.newArrayList();
//		// 解析数据
//		for (CreateRequest request : list) {
//			AssetSynVo parse = parse(request);
//			if (Objects.isNull(parse)) {
//				continue;
//			}
//			List<AssetSynMessageSaveItem> first = parse.getFirst();
//			List<AssetSynMessageSaveItem> second = parse.getSecond();
//			AssetSynMessageSaveItem grand = parse.getGrand();
//			if (Objects.nonNull(grand)) {
//				targetList.add(grand);
//			}
//			if (CollectionUtil.isNotEmpty(first)) {
//				targetList.addAll(first);
//			}
//			if (CollectionUtil.isNotEmpty(second)) {
//				targetList.addAll(second);
//			}
//		}
//		if (CollectionUtil.isEmpty(targetList)) {
//			log.info("parseBatch targetList empty");
//			return Lists.newArrayList();
//		}
//		log.info("parseBatch targetList size:{}", targetList.size());
//		Set<String> goodsId = targetList.stream().map(AssetSynMessageSaveItem::getAsset).map(TblAsset::getGoodsId).collect(toSet());
//		// 全新数据
//		// 更新数据
//		// 不处理数据
//		Set<Long> queryExistWithGoogIds = queryExistWithGoogIds(goodsId).stream().map(TblAsset::getGoodsId).map(Longs::tryParse).collect(toSet());
//		// 数据库中没有的数据
//		List<AssetSynMessageSaveItem> insertData = targetList.stream().filter(x -> !queryExistWithGoogIds.contains(x.goodsId())).collect(Collectors.toList());
//		return insertPageBatch(insertData);
//	}

	private List<SyncResponseMsg> insertPageBatch(List<AssetSynMessageSaveItem> insertData) {
		if (CollectionUtil.isEmpty(insertData)) {
			log.info("insertPageBatch empty");
			return Lists.newArrayList();
		}
		List<SyncResponseMsg> resultList = Lists.newArrayList();
		List<List<AssetSynMessageSaveItem>> page = Lists.partition(insertData, assetSyncePageSize);
		for (List<AssetSynMessageSaveItem> list : page) {
			List<TblAsset> assetList = Lists.newArrayList();
			List<TblAssetExt> assetExtList = Lists.newArrayList();
			List<TblAssetStaticResource> staticResourceList = Lists.newArrayList();
			for (AssetSynMessageSaveItem item : list) {
				SyncResponseMsg convert = item.convert();
				if (Objects.nonNull(convert)) {
					resultList.add(convert);
				}
				assetList.add(item.getAsset());
				assetExtList.add(item.getAssetExt());
				staticResourceList.add(item.getAssetStaticResource());
			}
			if (CollectionUtil.isNotEmpty(assetList)) {
				this.saveBatch(assetList);
				this.iTblAssetExtService.saveBatch(assetExtList);
				this.iTblAssetStaticResourceService.saveBatch(staticResourceList);
			}
		}
		return resultList;
	}

//	@Override
//	public AssetSynVo parse(CreateRequest createRequest) {
//		String busiId = createRequest.getBusiId();
//		String typeCode = createRequest.getTypeCode();
//		AssetTypeEnum firstType = AssetTypeEnum.convert(typeCode);
//		if (Objects.isNull(firstType)) {
//			log.info("typeCode: {} 不是有效的 资产类型", typeCode);
//			return null;
//		}
//		ReferProtocolEnum tokenType = ReferProtocolEnum.convert(createRequest.getTokenType());
//		if (Objects.isNull(tokenType)) {
//			log.info("tokenType: {} 不是有效的 ReferProtocolEnum 类型", createRequest.getTokenType());
//			return null;
//		}
//
//		String metaDataStr = createRequest.getMetaData();
//		boolean json = JSONUtil.isTypeJSON(metaDataStr);
//		if (!json) {
//			log.info("metaDataStr: {} 不是有效的 json 数据格式", metaDataStr);
//			return null;
//		}
//		long id = IdUtil.getSnowflakeNextId();
//		// 地皮
//		if (firstType == AssetTypeEnum.GROUND) {
//			GroundMetaData metaData = JSONUtil.toBean(metaDataStr, GroundMetaData.class);
//			if (Objects.isNull(metaData)) {
//				log.info("GroundMetaData metaData convert json is null: {} 不是有效的 json 数据格式", metaDataStr);
//				return null;
//			}
//			GroundTypeEnum secondType = GroundTypeEnum.convert(metaData.getType());
//			if (Objects.isNull(secondType)) {
//				return null;
//			}
//			// 有可能先把经营证，后发地皮
//			// 资产主属性
//			TblAsset groundAsset = createRequest
//					.buildGroundAsset(id, Longs.tryParse(busiId), tokenType, firstType.code(), secondType.code(), metaData.getDesc());
//			// 资产扩展属性
//			TblAssetExt groundAssetExt = createRequest.buildGroundAssetExt(id, tokenType);
//			TblAssetStaticResource staticResource = buildStaticResource(id, createRequest.file(), createRequest.file(), FileTypeEnum.IMG);
//
//			MetaDataParams buildMetaData = buildGroundMetaData(groundAsset, staticResource.getDisplayUri(), secondType, metaData);
//			groundAssetExt.setMetadata(JSONUtil.toJsonStr(buildMetaData));
//			// 地皮可能不入库，把地皮的metadata信息放到经营证上
//			// A类经营证 入库
//			List<AssetSynMessageSaveItem> first = flatCertificate(groundAsset, groundAssetExt, staticResource, metaData
//					.getFirst(), CertificateTypeEnum.A, metaData);
//			// B类经营证 入库
//			List<AssetSynMessageSaveItem> second = flatCertificate(groundAsset, groundAssetExt, staticResource, metaData
//					.getSecond(), CertificateTypeEnum.B, metaData);
//			// 如果是商业地皮,没有拥有人, 数据不处理, 只处理经营证
//			AssetSynMessageSaveItem grand = null;
//			// 有拥有人
//			// 有开放类型
//			// 有支付方式
//			// 价格大于0
//			if (groundAsset.filter()) {
//				grand = AssetSynMessageSaveItem.builder().asset(groundAsset).assetExt(groundAssetExt).assetStaticResource(staticResource).build();
//			}
//			return AssetSynVo.builder().grand(grand).first(first).second(second).build();
//
//		} else if (firstType == AssetTypeEnum.BIZ_CERTIFICATE) {
//			CertificateMetaData bean = JSONUtil.toBean(metaDataStr, CertificateMetaData.class);
//			if (Objects.isNull(bean)) {
//				log.info("CertificateMetaData metaData convert json is null: {} 不是有效的 json 数据格式", metaDataStr);
//				return null;
//			}
//			String landId = bean.getLandId();
//			if (StringUtils.isBlank(landId)) {
//				log.info("landId is empty bean obj: {}", JSONUtil.toJsonStr(bean));
//				return null;
//			}
//			CertificateTypeEnum secondType = CertificateTypeEnum.convert(bean.getType());
//			if (Objects.isNull(secondType)) {
//				log.info("secondType is null bean obj: {}", JSONUtil.toJsonStr(bean));
//				return null;
//			}
//			TblAsset groundAsset = createRequest
//					.buildGroundAsset(id, Longs.tryParse(landId), tokenType, firstType.code(), secondType.code(), StringUtils.EMPTY);
//			TblAssetExt groundAssetExt = createRequest.buildGroundAssetExt(id, tokenType);
//			TblAssetStaticResource staticResource = buildStaticResource(id, createRequest.getFileUrl(), createRequest.getFileUrl(), FileTypeEnum.IMG);
//			AssetSynMessageSaveItem grand = AssetSynMessageSaveItem
//					.builder()
//					.asset(groundAsset)
//					.assetExt(groundAssetExt)
//					.assetStaticResource(staticResource)
//					.build();
//			return AssetSynVo.builder().grand(grand).first(Lists.newArrayList()).second(Lists.newArrayList()).build();
//		}
//		return null;
//	}

//	public List<AssetSynMessageSaveItem> flatCertificate(TblAsset groundAsset, TblAssetExt groundAssetExt, TblAssetStaticResource staticResource,
//			List<CertificateData> list, CertificateTypeEnum type, GroundMetaData groundMetaData) {
//		List<AssetSynMessageSaveItem> result = Lists.newArrayList();
//		CerGroundMetaData ground = CerGroundMetaData
//				.builder()
//				.address(groundMetaData.getAddress())
//				.area(groundMetaData.getArea())
//				.lat(groundMetaData.getLat())
//				.lon(groundMetaData.getLon())
//				.desc(groundMetaData.getDesc())
//				.type(groundMetaData.getType())
//				.level(groundMetaData.getLevel())
//				.fileUrl(staticResource.getFileUri())
//				.name(groundAsset.getAssetName())
//				.firstTotal(groundMetaData.getFirstTotal())
//				.secondTotal(groundMetaData.getSecondTotal())
//				.build();
//		for (CertificateData cer : list) {
//			if (StringUtils.isBlank(cer.getOwnerId())) {
//				continue;
//			}
//			TblAsset buildCerAsset = cer.buildAsset(groundAsset, type, cer.getBusId());
//			if (!buildCerAsset.filter()) {
//				log.info("flatCertificate: {}", JSONUtil.toJsonStr(buildCerAsset));
//				continue;
//			}
//			CertificateMetaData build = CertificateMetaData.builder().landId(groundAsset.getGoodsId()).ground(ground).type(type.name()).build();
//			buildCerAsset.setMetaData(JSONUtil.toJsonStr(build));
//			TblAssetExt buildCerAssetExt = cer.buildAssetExt(groundAssetExt, buildCerAsset.getId());
//			TblAssetStaticResource buildStaticResource = buildStaticResource(buildCerAsset.getId(), cer.file(), cer.file(), FileTypeEnum.IMG);
//			MetaDataParams certificateMetaData = buildCertificateMetaData(groundAsset, buildStaticResource.getDisplayUri(), type, groundMetaData, build);
//			buildCerAssetExt.setMetadata(JSONUtil.toJsonStr(certificateMetaData));
//			AssetSynMessageSaveItem item = AssetSynMessageSaveItem
//					.builder()
//					.asset(buildCerAsset)
//					.assetExt(buildCerAssetExt)
//					.assetStaticResource(buildStaticResource)
//					.build();
//			result.add(item);
//		}
//		return result;
//	}

	@Override
	public List<AssetVo> queryAssetType(String uid, AssetTypeEnum type) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		query.in(TblAsset::getFirstType, type.code());
		query.eq(TblAsset::getOwnerId, uid);
		Page<TblAsset> page = query.page(new Page<>(0, assetTypePageSize));
		List<TblAsset> records = page.getRecords();
		if (CollectionUtil.isEmpty(records)) {
			return Lists.newArrayList();
		}
		Set<Long> assetId = records.stream().map(TblAsset::getId).collect(toSet());
		Map<Long, TblAssetExt> assetExtMap = this.iTblAssetExtService
				.infoWithAssetIds(assetId)
				.stream()
				.collect(toMap(TblAssetExt::getAssetId, Function.identity(), (a, b) -> b));
		Map<Long, TblAssetStaticResource> assetStaticMap = this.iTblAssetStaticResourceService
				.infoWithAssetIds(assetId)
				.stream()
				.collect(toMap(TblAssetStaticResource::getAssetId, Function.identity(), (a, b) -> b));
		return records.stream().map(x -> one(x, assetExtMap, assetStaticMap)).collect(toList());
	}

	private AssetVo one(TblAsset asset, Map<Long, TblAssetExt> assetExtMap, Map<Long, TblAssetStaticResource> assetStaticMap) {
		Function<? super TblAssetStaticResource, String> displayUrlMapper = TblAssetStaticResource::getDisplayUri;
		Function<? super TblAssetStaticResource, String> logoUriMapper = TblAssetStaticResource::getLogoUri;
		String displayUri = Optional.ofNullable(assetStaticMap.get(asset.getId())).map(displayUrlMapper).orElse(StringUtils.EMPTY);
		String logoUri = Optional.ofNullable(assetStaticMap.get(asset.getId())).map(logoUriMapper).orElse(StringUtils.EMPTY);
		String assetHash = Optional.ofNullable(assetExtMap.get(asset.getId())).map(TblAssetExt::getChainTxHash).orElse(StringUtils.EMPTY);
		return AssetVo
				.builder()
				.assetId(String.valueOf(asset.getId()))
				.assetName(asset.getAssetName())
				.displayUri(displayUri)
				.logoUri(logoUri)
				.firstType(asset.getFirstType())
				.secondType(asset.getSecondType())
				.assetPrice(asset.getPrice())
				.priceUnit(String.valueOf(asset.getUnit()))
				.assetHash(assetHash)
				.showState(String.valueOf(asset.getShowState()))
				.goodsId(asset.getGoodsId())
				.build();
	}

	private AssetTypeItem buildItem(List<AssetVo> asset, AssetTypeEnum type, String moreUrl, String detailUrl) {
		List<AssetTypeSubItem> list = asset.stream().map(x -> one(x, detailUrl)).collect(toList());
		return AssetTypeItem.builder().typeName(type.desc()).nums((long) list.size()).moreLink(moreUrl + type.outMyAssetType()).list(list).build();
	}

	private AssetTypeSubItem one(AssetVo asset, String detailUrl) {
		String link = Optional.ofNullable(asset).map(AssetVo::getGoodsId).filter(StringUtils::isNotBlank).map(x -> detailUrl + x).orElse(StringUtils.EMPTY);
		return AssetTypeSubItem
				.builder()
				.name(asset.getAssetName())
				.image(asset.getDisplayUri())
				.basePrice(String.valueOf(asset.getAssetPrice()))
				.priceType(asset.getPriceUnit())
				.chainAddress(asset.getAssetHash())
				.link(link)
				.goodsId(asset.getGoodsId())
				.title(asset.getAssetName())
				.content(ASSET_CONTENT)
				.build();
	}

	private AssetTypeItem buildItemEx(AssetTypeSubIPagetem asset, AssetTypeEnum type, String moreUrl, String detailUrl) {
		List<AssetTypeSubItem> list = asset.getList().stream().map(item -> {
			if (StringUtils.isNotBlank(item.getGoodsId())) {
				item.setLink(detailUrl + item.getGoodsId());
			}
			return item;
		}).collect(toList());
		return AssetTypeItem.builder().typeName(type.desc()).nums(asset.getTotal()).moreLink(moreUrl + type.outMyAssetType()).list(list).build();
	}

	public AssetTypeSubIPagetem queryAssetTypeEx(String uid, List<Integer> codes) {
//		MPJLambdaWrapper<TblAsset> pageQuery = JoinWrappers
//				.lambda(TblAsset.class)
//				.select(TblAsset::getId)
//				.eq(TblAsset::getOwnerId, uid)
//				.in(TblAsset::getFirstType, codes);
//		// 只是为了得到总数
//		Page<TblAsset> page = pageQuery.page(new Page<TblAsset>(1, 1));
//		long total = page.getTotal();
//		String last = String.format("limit %d, %d", 0, assetTypePageSize);
		MPJLambdaWrapper<TblAssetAccount> leftJoin = JoinWrappers
				.lambda(TblAssetAccount.class)
				.selectAs(TblAsset::getAssetName, AssetTypeSubItem::getName)
				.selectAs(TblAsset::getPrice, AssetTypeSubItem::getBasePrice)
				.selectAs(TblAsset::getUnit, AssetTypeSubItem::getPriceType)
				.selectAs(TblAsset::getGoodsId, AssetTypeSubItem::getGoodsId)
				.selectAs(TblAsset::getAssetName, AssetTypeSubItem::getTitle)
				.select(TblAsset::getAssetName, TblAsset::getId, TblAsset::getFirstType, TblAsset::getSecondType, TblAsset::getShowState, TblAsset::getGoodsId)
//				.from(x -> x
//						.selectAll()
//						.eq(TblAsset::getOwnerId, uid)
//						.in(TblAsset::getFirstType, codes)
//						.orderByDesc(TblAsset::getLastModifyDatetime)
//						.last(last))
				.leftJoin(TblAsset.class, TblAsset::getId, TblAssetAccount::getAssetId)
				.selectAs(TblAssetExt::getChainTxHash, AssetTypeSubItem::getChainAddress)
				.leftJoin(TblAssetExt.class, TblAssetExt::getAssetId, TblAsset::getId)
				.selectAs(TblAssetStaticResource::getDisplayUri, AssetTypeSubItem::getImage)
				.leftJoin(TblAssetStaticResource.class, TblAssetStaticResource::getAssetId, TblAsset::getId)
				.eq(TblAssetAccount::getOwnerId, Longs.tryParse(uid))
				.in(TblAsset::getFirstType, codes)
				.orderByDesc(TblAssetAccount::getUpdateTime);
		Page<AssetTypeSubItem> pageList = leftJoin.page(new Page<AssetTypeSubItem>(0, assetTypePageSize), AssetTypeSubItem.class);
		List<AssetTypeSubItem> records = pageList.getRecords();
		// List<AssetTypeSubItem> list = leftJoin.list(AssetTypeSubItem.class);
//		return AssetTypeSubIPagetem.builder().list(list).total(total).build();
		return AssetTypeSubIPagetem.builder().list(records).total(pageList.getTotal()).build();
	}

//	private MetaDataParams buildGroundMetaData(TblAsset asset, String image, GroundTypeEnum secondType, GroundMetaData metaData) {
//		AssetTypeEnum assetType = secondType.assetType();
//		MetaDataParams params = new MetaDataParams();
//		params.setDescription(StringUtils.EMPTY);
//		params.setName(asset.getAssetName());
//		params.setImage(image);
//		List<MetaDataAttribute> attributes = Lists.newArrayList();
//		attributes.add(new MetaDataAttribute("地址", metaData.getAddress()));
//		attributes.add(new MetaDataAttribute("面称", String.valueOf(metaData.getArea())));
//		attributes.add(new MetaDataAttribute("经度", metaData.getLon()));
//		attributes.add(new MetaDataAttribute("纬度", metaData.getLat()));
//		attributes.add(new MetaDataAttribute("类型", assetType.desc()));
//		attributes.add(new MetaDataAttribute("分类", secondType.desc()));
//		attributes.add(new MetaDataAttribute("城市", metaData.getCitiy()));
//		params.setAttributes(attributes);
//		return params;
//	}

//	private MetaDataParams buildCertificateMetaData(TblAsset asset, String image, CertificateTypeEnum assetType, GroundMetaData metaData,
//			CertificateMetaData certificate) {
//		MetaDataParams params = new MetaDataParams();
//		params.setDescription(StringUtils.EMPTY);
//		params.setName(asset.getAssetName());
//		params.setImage(image);
//		List<MetaDataAttribute> attributes = Lists.newArrayList();
//		attributes.add(new MetaDataAttribute("所属地皮", metaData.getAddress()));
//		attributes.add(new MetaDataAttribute("地皮编号", String.valueOf(metaData.getAddressNumber())));
//		attributes.add(new MetaDataAttribute("类型", assetType.desc()));
//		params.setAttributes(attributes);
//		return params;
//	}

	@Override
	public Long childCount(Long parentId) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.select(TblAsset::getId).eq(TblAsset::getParentId, parentId).ne(TblAsset::getGoodsId, String.valueOf(parentId)).count();
	}

	@Override
	public Long nextSeqNoFake(Long parentId) {
		BoundHashOperations<String, String, Long> fakeBoundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:fake");
		Long increment = fakeBoundHashOps.increment(String.valueOf(parentId), 0);
		if (Objects.equals(0L, increment)) {
//			Long childCount = childCount(parentId);
			Map<String, String> fakeLimit = Splitter.on("&").withKeyValueSeparator("=").split(fakeStart);
			Long fakeStartVal = Longs.tryParse(fakeLimit.getOrDefault(String.valueOf(parentId), "0"));
			Long curr = fakeBoundHashOps.increment(String.valueOf(parentId), fakeStartVal);
			return curr + 1;
		}
		return increment + 1;
	}

	@Override
	public Long nextSeqNo(Long parentId) {
		BoundHashOperations<String, String, Long> fakeBoundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:act");
		Long increment = fakeBoundHashOps.increment(String.valueOf(parentId), 0);
		if (Objects.equals(0L, increment)) {
			Long childCount = childCount(parentId);
			return childCount + 1;
		}
		return increment + 1;
	}

	/**
	 * @param parentId
	 * @return
	 */
	@Override
	public Long nextSeqNo(Long parentId, Boolean isOpenFake) {
		String redisKey = "nft:seqno:act";
		if (Objects.equals(Boolean.TRUE, isOpenFake)) {
			redisKey = "nft:seqno:fake";
		}
		BoundHashOperations<String, String, Long> fakeBoundHashOps = stringRedisTemplate.boundHashOps(redisKey);
		Long increment = fakeBoundHashOps.increment(String.valueOf(parentId), 0);
		if (Objects.equals(0L, increment)) {
			Long childCount = childCount(parentId);
			return childCount + 1;
		}
		return increment + 1;
	}

	@Override
	public Long updateSeqNo(Long parentId, Long plus) {
		BoundHashOperations<String, String, Long> fakeBoundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:fake");
		Long increment = fakeBoundHashOps.increment(String.valueOf(parentId), plus);
		BoundHashOperations<String, String, Long> actBoundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:act");
		actBoundHashOps.increment(String.valueOf(parentId), 1);
		return increment;
	}

	@Override
	public Long roseSellStatis(Long parentId, boolean third) {
		BoundHashOperations<String, String, Long> boundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:act");
		String key = String.valueOf(parentId);
		if (third) {
			key += ":third";
		}
		Long increment = boundHashOps.increment(key, 1L);
		return increment;
	}

	@Override
	public TblAsset infoByParentIdAndSecondType(String busId, Integer secondType, String ownerUid) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query
				.eq(TblAsset::getParentId, Longs.tryParse(busId))
				.eq(TblAsset::getOwnerId, ownerUid)
				.eq(TblAsset::getSecondType, secondType)
				.last("for update")
				.one();
	}

	@Override
	public Boolean assetAmountIncr(Long id, Integer secondType, String ownerUid) {
		LambdaUpdateChainWrapper<TblAsset> update = new LambdaUpdateChainWrapper<>(baseMapper);
		return update
				.setIncrBy(TblAsset::getAmount, 1)
				.eq(TblAsset::getId, id)
				.eq(TblAsset::getOwnerId, ownerUid)
				.eq(TblAsset::getSecondType, secondType)
				.update();
	}

	@Override
	public Boolean assetPopularity(String goodsId, BigDecimal popularity) {
		LambdaUpdateChainWrapper<TblAsset> update = new LambdaUpdateChainWrapper<>(baseMapper);
		return update.setIncrBy(TblAsset::getPopularity, popularity).eq(TblAsset::getGoodsId, goodsId).update();
	}

	@Override
	public List<NftListVo> getIndexList(Long uid, Integer currentPage, Integer pageSize, SortType sortType, SortOrder sortOrder) {
		currentPage = Optional.ofNullable(currentPage).filter(Objects::nonNull).filter(x -> x > 0).orElse(1);
		String page = String.format("limit %d, %d", ((currentPage - 1) * pageSize), pageSize).toString();
		List<NftListVo> list = new ArrayList<>();
		if (currentPage.equals(1)) {
			list = getIndexCacheListByType(page, sortType, sortOrder);
		} else {
			list = getIndexListByType(page, sortType, sortOrder);
		}
		return list;
	}

	/**
	 * 返回显示内容， 0 空， 1 公示， 2预售
	 * 
	 * @param nftListVo
	 * @return
	 */

	public Map<Long, UserInfoResponse> queryUsers(Collection<Long> uids) {
		if (CollectionUtil.isEmpty(uids)) {
			return null;
		}
		List<UserInfoResponse> userInfo = iInnerService.userInfo(uids);
		return userInfo.stream().collect(Collectors.toMap(UserInfoResponse::getAppUserId, Function.identity(), (a, b) -> b));
	}

	private List<NftListVo> getIndexCacheListByType(String page, SortType sortType, SortOrder sortOrder) {
		return cacheManager
				.cacheList(() -> getIndexListByType(page, sortType, sortOrder), AssetCache.INDEX_LIST, sortType.getValue() + "_" + sortOrder.getValue());
	}

	public List<NftListVo> getIndexListByType(String page, SortType sortType, SortOrder sortOrder) {

		MPJLambdaWrapper<TblAssetAccount> leftJoin = JoinWrappers
				.lambda(TblAssetAccount.class)
				.leftJoin(TblAsset.class, TblAsset::getId, TblAssetAccount::getAssetId)
				.leftJoin(TblAssetExt.class, TblAssetExt::getAssetId, TblAsset::getId)
				.leftJoin(TblAssetStaticResource.class, TblAssetStaticResource::getAssetId, TblAsset::getId)

				.select(TblAssetAccount::getAmount)
				.select(TblAssetAccount::getVer)
				.select(TblAssetAccount::getPrice)
				.select(TblAssetAccount::getGuidePrice)
				.select(TblAssetAccount::getLastPutOnSaleDatetime)
				.select(TblAssetAccount::getSellPattern)
				.select(TblAssetAccount::getOwnerId)
				.selectAs(TblAssetAccount::getAmount, "ownerAmount")
				.selectAs(TblAssetAccount::getId, "assetAccountId")
				.select("t.price * t.ver as hot")
				.selectFunc("%s*%s", arg -> arg.accept(TblAssetAccount::getPrice, TblAssetAccount::getVer), NftListVo::getHot)
				.select(TblAssetExt::getPublicityPeriodStart, TblAssetExt::getPublicityPeriodEnd, TblAssetExt::getPreSellStartDatetime)
				.selectAs(TblAssetExt::getSellStartDatetime, "PreSellEndDatetime")
				.select(TblAssetStaticResource::getLogoUri, TblAssetStaticResource::getAssetId)
				.select(TblAsset::getGoodsId, TblAsset::getFirstType, TblAsset::getSecondType, TblAsset::getAssetName, TblAsset::getUnit, TblAsset::getDescription, TblAsset::getPopularityRate, TblAsset::getAuthorId)
				.select(TblAsset::getOwnerType)
				.eq(TblAssetAccount::getVisiable, StatusAllEnum.YES.code())
				.eq(TblAssetAccount::getSellPattern, SellPatternEnum.BUY_NOW_PRICE.code())
				.gt(TblAssetAccount::getAmount, 0);

		// 根据排序类型和顺序添加排序条件
		applySort(sortType, sortOrder, leftJoin);
		leftJoin.orderByDesc(TblAsset::getId);
		leftJoin.last(page);
		return leftJoin.list(NftListVo.class);

	}

	public void applySort(SortType sortType, SortOrder sortOrder, MPJLambdaWrapper<TblAssetAccount> leftJoin) {
		long currentTimeMillis = System.currentTimeMillis();

		switch (sortType) {
		case PUT_ON_SELL_DATETIME:
			applyOrderBy(leftJoin, sortOrder, TblAssetAccount::getLastPutOnSaleDatetime);
			break;
		case PRICE:

			applyOrderBy(leftJoin, sortOrder, TblAssetAccount::getPrice);
			break;
		case PRE_SALE:
			leftJoin.le(TblAssetExt::getPreSellStartDatetime, currentTimeMillis);
			leftJoin.gt(TblAssetExt::getSellStartDatetime, currentTimeMillis);
			applyOrderBy(leftJoin, sortOrder, TblAssetExt::getPreSellStartDatetime);
			break;
		case ON_SALE:
			leftJoin.le(TblAssetExt::getSellStartDatetime, currentTimeMillis);
			applyOrderBy(leftJoin, sortOrder, TblAssetExt::getSellStartDatetime);
			break;
		case SELF_OPERATED:
			leftJoin.eq(TblAsset::getOwnerType, OwnerTypeEnum.PLATFORM.code());
			applyOrderBy(leftJoin, sortOrder, "hot");
			break;
		case CREATE_DATETIME:
			applyOrderBy(leftJoin, sortOrder, TblAsset::getCreateDatetime);
			break;
		case END_DATETIME:
			applyOrderBy(leftJoin, sortOrder, TblAssetExt::getSellEndDatetime);
			break;
		default:
			applyOrderBy(leftJoin, SortOrder.DESC, "hot");
		}
	}

	private void applyOrderBy(MPJLambdaWrapper<TblAssetAccount> leftJoin, SortOrder sortOrder, String field) {
		if (sortOrder == SortOrder.ASC) {
			leftJoin.orderByAsc(field);
		} else {
			leftJoin.orderByDesc(field);
		}
	}

	private <R> void applyOrderBy(MPJLambdaWrapper<TblAssetAccount> leftJoin, SortOrder sortOrder, SFunction<R, ?> fieldGetter) {
		if (sortOrder == SortOrder.ASC) {
			leftJoin.orderByAsc(fieldGetter);
		} else {
			leftJoin.orderByDesc(fieldGetter);
		}
	}

	@Override
	public Map<Long, Long> getSeqNo() {
		BoundHashOperations<String, String, String> boundHashOps = stringRedisTemplate.boundHashOps("nft:seqno:fake");
		Set<String> keys = boundHashOps.keys();
		HashMap<Long, Long> newHashMap = Maps.newHashMap();
		if (keys != null && !keys.isEmpty()) {
			keys.forEach(k -> {
				newHashMap.put(Longs.tryParse(k), Longs.tryParse(Objects.requireNonNull(boundHashOps.get(k))));
			});
			return newHashMap;
		}
		// 从配置中读取默认值
		Map<String, String> fakeLimit = Splitter.on("&").withKeyValueSeparator("=").split(fakeStart);
		fakeLimit.forEach((key, value) -> {
			newHashMap.put(Longs.tryParse(key), Longs.tryParse(value));
		});
		return newHashMap;
	}

	@Override
	public AssetBaseInfo infoGoodsId(String goodsId) {
		TblAsset asset = assetInfoWithGoodsId(goodsId);
		if (Objects.isNull(asset)) {
			log.info("asset is null goodsId:{}", goodsId);
			return null;
		}
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(asset.getId());
		TblAssetStaticResource assetStatic = iTblAssetStaticResourceService.infoWithAssetIdByCache(asset.getId());
		return AssetBaseInfo.builder().asset(asset).assetExt(assetExt).assetStaticResource(assetStatic).build();
	}

	@Override
	public AssetBaseInfo infoAssetId(Long assetId) {
		TblAsset asset = this.getById(assetId);
		if (Objects.isNull(asset)) {
			log.info("asset is null assetId:{}", assetId);
			return null;
		}
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(asset.getId());
		TblAssetStaticResource assetStatic = iTblAssetStaticResourceService.infoWithAssetIdByCache(asset.getId());
		return AssetBaseInfo.builder().asset(asset).assetExt(assetExt).assetStaticResource(assetStatic).build();
	}

	public List<TblAsset> infoWithAssetIds(Collection<Long> assetIds) {
		if (CollectionUtil.isEmpty(assetIds)) {
			return Lists.newArrayList();
		}
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		return query.in(TblAsset::getId, assetIds).list();
	}

	@Override
	public Map<String, AssetVo> getAssetListByIds(Set<Long> assetIds) {
		List<TblAsset> records = infoWithAssetIds(assetIds);
		Map<Long, TblAssetExt> assetExtMap = this.iTblAssetExtService
				.infoWithAssetIds(assetIds)
				.stream()
				.collect(toMap(TblAssetExt::getAssetId, Function.identity(), (a, b) -> b));
		Map<Long, TblAssetStaticResource> assetStaticMap = this.iTblAssetStaticResourceService
				.infoWithAssetIds(assetIds)
				.stream()
				.collect(toMap(TblAssetStaticResource::getAssetId, Function.identity(), (a, b) -> b));
		Map<Long, TblAssetAccount> assetAccountMap = this.iTblAssetAccountService
				.infoWithAssetIds(assetIds)
				.stream()
				.collect(toMap(TblAssetAccount::getAssetId, Function.identity(), (a, b) -> b));
		return records
				.stream()
				.map(x -> one(x, assetExtMap, assetStaticMap, assetAccountMap))
				.collect(Collectors.toMap(AssetVo::getAssetId, Function.identity()));
	}

	private AssetVo one(TblAsset asset, Map<Long, TblAssetExt> assetExtMap, Map<Long, TblAssetStaticResource> assetStaticMap,
			Map<Long, TblAssetAccount> assetAccountMap) {
		Function<? super TblAssetStaticResource, String> displayUrlMapper = TblAssetStaticResource::getDisplayUri;
		Function<? super TblAssetStaticResource, String> LogoUriMapper = TblAssetStaticResource::getLogoUri;
		String displayUri = Optional.ofNullable(assetStaticMap.get(asset.getId())).map(displayUrlMapper).orElse(StringUtils.EMPTY);
		String logoUri = Optional.ofNullable(assetStaticMap.get(asset.getId())).map(LogoUriMapper).orElse(StringUtils.EMPTY);
		String assetHash = Optional.ofNullable(assetExtMap.get(asset.getId())).map(TblAssetExt::getChainTxHash).orElse(StringUtils.EMPTY);
		BigDecimal amount = Optional.ofNullable(assetAccountMap.get(asset.getId())).map(TblAssetAccount::getAmount).orElse(BigDecimal.ZERO);
		return AssetVo
				.builder()
				.assetId(String.valueOf(asset.getId()))
				.assetName(asset.getAssetName())
				.displayUri(displayUri)
				.logoUri(logoUri)
				.firstType(asset.getFirstType())
				.secondType(asset.getSecondType())
				.assetPrice(asset.getPrice())
				.priceUnit(String.valueOf(asset.getUnit()))
				.assetHash(assetHash)
				.showState(String.valueOf(asset.getShowState()))
				.goodsId(asset.getGoodsId())
				.description(asset.getDescription())
				.popularity(Optional.ofNullable(asset.getPopularity()).orElse(BigDecimal.ZERO).toString())
				.amount(amount.intValue())
				.build();
	}

	@Override
	public Long queryPutOn(String uid) {
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		Long count = query.eq(TblAsset::getOwnerId, uid).eq(TblAsset::getSellPattern, SellPatternEnum.BUY_NOW_PRICE.code()).last("limit 1").count();
		return Objects.isNull(count) ? 0 : count;
	}

	@Override
	public MyAssetsResponse getMyAssets(String uid, Integer currPage, Integer type) {
		AssetTypeEnum assetType = AssetTypeEnum.queryConvert(type);
		if (Objects.isNull(assetType)) {
			log.info("type is null: {}", type);
			return null;
		}
		List<AssetVo> list = queryAssetType(currPage, uid, assetType);
		return MyAssetsResponse.builder().list(list).build();
	}

	@Override
	public List<AssetVo> queryAssetType(Integer currPage, String uid, AssetTypeEnum type) {
		currPage = Optional.ofNullable(currPage).orElse(0);
		LambdaQueryChainWrapper<TblAsset> query = new LambdaQueryChainWrapper<>(baseMapper);
		if (type == AssetTypeEnum.GROUND) {
			query.in(TblAsset::getFirstType, Arrays.asList(AssetTypeEnum.GROUND.code(), AssetTypeEnum.BIZ_CERTIFICATE.code()));
		} else {
			query.eq(TblAsset::getFirstType, type.code());
		}
		if (type != AssetTypeEnum.MISS_ASIA_BLIND_BOX) {
			query.eq(TblAsset::getOwnerId, uid);
		}
		Page<TblAsset> page = query.page(new Page<>(currPage, pageSize));
		List<TblAsset> records = page.getRecords();
		if (CollectionUtil.isEmpty(records)) {
			return Lists.newArrayList();
		}
		Set<Long> assetId = records.stream().map(TblAsset::getId).collect(toSet());
		Map<Long, TblAssetExt> assetExtMap = this.iTblAssetExtService
				.infoWithAssetIds(assetId)
				.stream()
				.collect(toMap(TblAssetExt::getAssetId, Function.identity(), (a, b) -> b));
		Map<Long, TblAssetStaticResource> assetStaticMap = this.iTblAssetStaticResourceService
				.infoWithAssetIds(assetId)
				.stream()
				.collect(toMap(TblAssetStaticResource::getAssetId, Function.identity(), (a, b) -> b));
		Map<Long, TblAssetAccount> assetAccountMap = this.iTblAssetAccountService
				.infoWithAssetIds(assetId)
				.stream()
				.collect(toMap(TblAssetAccount::getAssetId, Function.identity(), (a, b) -> b));
		return records.stream().map(x -> one(x, assetExtMap, assetStaticMap, assetAccountMap)).collect(toList());
	}

	@Override
	public TblAssetExpandDto getAssetExpand(Long assetId) {
		TblAsset asset = getById(assetId);
		TblAssetExt assetExt = iTblAssetExtService.infoWithAssetIdByCache(assetId);
		TblAssetUserLog assetUserLog = iTblAssetUserLogService.lastConfigWithCache(assetId);
		return convertAsset2ExpandDto(asset, assetExt, assetUserLog);
	}

	private TblAssetExpandDto convertAsset2ExpandDto(TblAsset asset, TblAssetExt assetExt, TblAssetUserLog assetUserLog) {
		if (Objects.isNull(asset)) {
			return null;
		}
		TblAssetExpandDto dto = new TblAssetExpandDto();
		BeanUtil.copyProperties(asset, dto, "tblAssetExtDto", "tblAssetUserLogDto");
		if (Objects.nonNull(assetExt)) {
			dto.setTblAssetExtDto(convertAssetExt2Dto(assetExt));
		}
		if (Objects.nonNull(assetUserLog)) {
			dto.setTblAssetUserLogDto(convertLog2Dto(assetUserLog));
		}
		return dto;
	}

	private TblAssetExtDto convertAssetExt2Dto(TblAssetExt assetExt) {
		if (Objects.isNull(assetExt)) {
			return null;
		}
		TblAssetExtDto dto = new TblAssetExtDto();
		BeanUtil.copyProperties(assetExt, dto);
		return dto;
	}

	private TblAssetUserLogDto convertLog2Dto(TblAssetUserLog assetUserLog) {
		if (Objects.isNull(assetUserLog)) {
			return null;
		}
		TblAssetUserLogDto dto = new TblAssetUserLogDto();
		BeanUtil.copyProperties(assetUserLog, dto);
		return dto;
	}

	@Override
	public boolean updateAsset(Long id, BigDecimal price, BigDecimal guidePrice, SellPatternEnum oldSellPattern, SellPatternEnum newSellPattern) {
		LambdaUpdateChainWrapper<TblAsset> update = new LambdaUpdateChainWrapper<>(baseMapper);
		return update
				.eq(TblAsset::getId, id)
				.eq(TblAsset::getSellPattern, oldSellPattern.code())
				.set(TblAsset::getPrice, price)
				.set(TblAsset::getSellPattern, newSellPattern.code())
				.set(TblAsset::getGuidePrice, guidePrice)
				.update();
	}

	@Override
	public boolean updateSellPattern(Long assetId, SellPatternEnum oldSellPattern, SellPatternEnum newSellPattern) {
		LambdaUpdateChainWrapper<TblAsset> updateWrapper = new LambdaUpdateChainWrapper<>(this.baseMapper);
		updateWrapper
				.eq(TblAsset::getId, assetId)
				.eq(TblAsset::getSellPattern, oldSellPattern.code())
				.set(TblAsset::getSellPattern, newSellPattern.code())
				.set(TblAsset::getLastModifyDatetime, System.currentTimeMillis());
		boolean update = updateWrapper.update();
		if (!update) {
			log.info("updateShowState goodsId: {}, oldSate: {}, newSate: {}", assetId, oldSellPattern, newSellPattern);
		}
		return update;
	}

	@Override
	public Map<String, AssetAllInfoResponse> batchInfoGoodsIds(Collection<String> goodsIds) {
		List<AssetInfoResponse> listAsset = queryBatchAssetInfoResponseWithGoodsId(goodsIds);
		if (CollectionUtil.isEmpty(listAsset)) {
			return Maps.newHashMap();
		}
		Map<Long, AssetInfoResponse> assetMap = listAsset.stream().collect(toMap(AssetInfoResponse::getId, Function.identity()));
		Set<Long> assetIds = assetMap.keySet();
		List<AssetExtInfoResponse> listAssetExt = this.iTblAssetExtService.queryBatchAssetExtInfoResponse(assetIds);
		Map<Long, AssetExtInfoResponse> AssetExtMap = listAssetExt.stream().collect(toMap(AssetExtInfoResponse::getAssetId, Function.identity()));
		List<AssetStaticResponse> listAsstStatic = this.iTblAssetStaticResourceService.queryBatchAssetStaticResponse(assetIds);
		Map<Long, AssetStaticResponse> assetStaticMap = listAsstStatic.stream().collect(toMap(AssetStaticResponse::getAssetId, Function.identity()));
		Map<String, AssetAllInfoResponse> resultMap = Maps.newHashMap();
		assetMap.forEach((k, v) -> {
			AssetExtInfoResponse assetExt = AssetExtMap.get(k);
			AssetStaticResponse assetStatic = assetStaticMap.get(k);
			AssetAllInfoResponse response = AssetAllInfoResponse.builder().asset(v).assetExt(assetExt).assetStatic(assetStatic).build();
			resultMap.put(v.getGoodsId(), response);
		});
		return resultMap;
	}

	@Override
	public Map<Long, AssetAllInfoResponse> batchInfoAssetId(Collection<Long> assetIds) {
		List<AssetInfoResponse> listAsset = queryBatchAssetInfoResponseWithAssetId(assetIds);
		if (CollectionUtil.isEmpty(listAsset)) {
			return Maps.newHashMap();
		}
		Map<Long, AssetInfoResponse> assetMap = listAsset.stream().collect(toMap(AssetInfoResponse::getId, Function.identity()));
		List<AssetExtInfoResponse> listAssetExt = this.iTblAssetExtService.queryBatchAssetExtInfoResponse(assetIds);
		Map<Long, AssetExtInfoResponse> AssetExtMap = listAssetExt.stream().collect(toMap(AssetExtInfoResponse::getAssetId, Function.identity()));
		List<AssetStaticResponse> listAsstStatic = this.iTblAssetStaticResourceService.queryBatchAssetStaticResponse(assetIds);
		Map<Long, AssetStaticResponse> assetStaticMap = listAsstStatic.stream().collect(toMap(AssetStaticResponse::getAssetId, Function.identity()));
		Map<Long, AssetAllInfoResponse> resultMap = Maps.newHashMap();
		assetMap.forEach((k, v) -> {
			AssetExtInfoResponse assetExt = AssetExtMap.get(k);
			AssetStaticResponse assetStatic = assetStaticMap.get(k);
			AssetAllInfoResponse response = AssetAllInfoResponse.builder().asset(v).assetExt(assetExt).assetStatic(assetStatic).build();
			resultMap.put(k, response);
		});
		return resultMap;
	}

	@Override
	public List<AssetInfoResponse> queryBatchAssetInfoResponseWithAssetId(Collection<Long> assetIds) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers.lambda(TblAsset.class).in(TblAsset::getId, assetIds);
		List<AssetInfoResponse> list = query.list(AssetInfoResponse.class);
		return list;
	}

	@Override
	public List<AssetInfoResponse> queryBatchAssetInfoResponseWithGoodsId(Collection<String> goodsId) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers.lambda(TblAsset.class).in(TblAsset::getGoodsId, goodsId);
		List<AssetInfoResponse> list = query.list(AssetInfoResponse.class);
		return list;
	}

	@Override
	public AssetInfoResponse queryAssetInfoResponseInfoWithAssetId(Long assetId) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers.lambda(TblAsset.class).eq(TblAsset::getId, assetId);
		AssetInfoResponse one = query.one(AssetInfoResponse.class);
		return one;
	}

	@Override
	public AssetInfoResponse queryAssetInfoResponseInfoWithGoodsId(String goodsId) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers.lambda(TblAsset.class).eq(TblAsset::getGoodsId, goodsId);
		AssetInfoResponse one = query.one(AssetInfoResponse.class);
		return one;
	}

	@Override
	public AssetAllInfoResponse infoWithGoodsId(String goodsId) {
		log.info("asset infoWithGoodsId goodsId:{}", goodsId);
		AssetInfoResponse asset = queryAssetInfoResponseInfoWithGoodsId(goodsId);
		if (Objects.isNull(asset)) {
			log.info("asset is null goodsId:{}", goodsId);
			return null;
		}
		AssetExtInfoResponse assetExt = iTblAssetExtService.queryAssetExtInfoResponseInfo(asset.getId());
		AssetStaticResponse assetStatic = iTblAssetStaticResourceService.queryTblAssetStaticResourceInfo(asset.getId());
		return AssetAllInfoResponse.builder().asset(asset).assetExt(assetExt).assetStatic(assetStatic).build();
	}

	@Override
	public AssetAllInfoResponse infoWithAssetId(Long assetId) {
		AssetInfoResponse asset = queryAssetInfoResponseInfoWithAssetId(assetId);
		if (Objects.isNull(asset)) {
			log.info("asset is null assetId:{}", assetId);
			return null;
		}
		AssetExtInfoResponse assetExt = iTblAssetExtService.queryAssetExtInfoResponseInfo(asset.getId());
		AssetStaticResponse assetStatic = iTblAssetStaticResourceService.queryTblAssetStaticResourceInfo(asset.getId());
		return AssetAllInfoResponse.builder().asset(asset).assetExt(assetExt).assetStatic(assetStatic).build();
	}

	@Override
	public Boolean adjustPopularityRate(String goodsId, int targetValue) {
		LambdaUpdateChainWrapper<TblAsset> update = new LambdaUpdateChainWrapper<>(baseMapper);
		update.eq(TblAsset::getGoodsId, goodsId);
		if (targetValue > 0) {
			update.setIncrBy(TblAsset::getPopularityRate, targetValue);
		} else if (targetValue < 0) {
			update.setDecrBy(TblAsset::getPopularityRate, targetValue);
		} else {
			return Boolean.FALSE;
		}
		return update.update();
	}

	/**
	 * 根据父id查询资产
	 *
	 * @param parentId
	 * @return
	 */
	@Override
	public List<TblAsset> getByParentIdCacheable(Long parentId) {
		return cacheManager
				.cacheList(() -> new LambdaQueryChainWrapper<TblAsset>(baseMapper).eq(TblAsset::getParentId, parentId).list(), AssetCache.ASSET_CHILDREN, String
						.valueOf(parentId));
	}

	@Override
	public void batchDisposeScriptRestList(List<TblAsset> sortedTblAssets, Integer batchSize) {
		int totalSize = sortedTblAssets.size();
		int fromIndex = 0;

		while (fromIndex < totalSize) {
			int toIndex = Math.min(fromIndex + batchSize, totalSize);
			List<TblAsset> batchList = sortedTblAssets.subList(fromIndex, toIndex);

			// 使用 MyBatis-Plus 的 updateBatchById 方法
			this.updateBatchById(batchList);

			// 更新起始位置
			fromIndex = toIndex;
		}
	}

	@Override
	public List<SecondAssetListVo> getBoxSecondListCache(TblAsset asset) {
		// 查询缓存
		return cacheManager.cacheList(() -> getBoxSecondList(asset), AssetCache.SECOND_ASSET_LIST, asset.getGoodsId());
	}

	@Override
	public List<SecondAssetListVo> getBoxSecondList(TblAsset asset) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers
				.lambda(TblAsset.class)
				.leftJoin(TblAssetStaticResource.class, TblAssetStaticResource::getAssetId, TblAsset::getId)
				.eq(TblAsset::getParentId, asset.getGoodsId())
				.ne(TblAsset::getId, asset.getId())
				.select(TblAsset::getId, TblAsset::getAssetName)
				.select(TblAssetStaticResource::getLogoUri);
		return baseMapper.selectJoinList(SecondAssetListVo.class, query);
	}

	@Override
	public List<SecondAssetListVo> getBoxSecondListCache(Long assetId, String goodsId) {
		// 查询缓存
		return cacheManager.cacheList(() -> getBoxSecondList(assetId, goodsId), AssetCache.SECOND_ASSET_LIST, goodsId);
	}

	private List<SecondAssetListVo> getBoxSecondList(Long assetId, String goodsId) {
		MPJLambdaWrapper<TblAsset> query = JoinWrappers
				.lambda(TblAsset.class)
				.leftJoin(TblAssetStaticResource.class, TblAssetStaticResource::getAssetId, TblAsset::getId)
				.eq(TblAsset::getParentId, goodsId)
				.ne(TblAsset::getId, assetId)
				.select(TblAsset::getId, TblAsset::getAssetName)
				.select(TblAssetStaticResource::getLogoUri);
		List<SecondAssetListVo> list = query.list(SecondAssetListVo.class);
		return list;
	}

	@Override
	public AssetDetailInfo detailInfo(String uid, String goodsId, Long accountId) {
		AssetAllInfoResponse allInfo = infoWithGoodsId(goodsId);
		AssetAccountResponse accountInfo = null;
		if (Objects.nonNull(accountId) && accountId.longValue() > 0) {
			accountInfo = this.tblAssetAccountService.accountInfoWithId(accountId);
		}
		AssetInfoResponse asset = allInfo.getAsset();
		Long assetId = asset.getId();
		Boolean chainOverDays = getChainOverDays(assetId, Longs.tryParse(uid));
		BigDecimal buyMaxPriceWithHistory = this.iTblAssetTransactionService.buyMaxPriceWithHistory(uid, asset.getParentId());
		TblAssetGroup queryAssetGroup = iTblAssetGroupService.queryAssetGroup(assetId);
		AssetDetailInfo build = AssetDetailInfo
				.builder()
				.asset(asset)
				.assetExt(allInfo.getAssetExt())
				.assetStatic(allInfo.getAssetStatic())
				.assetAccount(accountInfo)
				.groupId(Objects.nonNull(queryAssetGroup) ? queryAssetGroup.getThirdGroupId() : "0")
				.buyMaxPriceWithHistory(buyMaxPriceWithHistory)
				.chainOverDays(chainOverDays)
				.build();
		TblAssetTransaction notPaiedTransaction = iTblAssetTransactionService.queryNotPaiedPersonalMintUpChaninOrder(assetId, uid);
		if (Objects.nonNull(notPaiedTransaction)) {
			build.setUpChainPaying(true);
		}
		if (Objects.nonNull(asset) && Objects.equals(asset.getFirstType(), AssetTypeEnum.BLIND_BOX.code())) {
			List<SecondAssetListVo> boxSecondListCache = getBoxSecondListCache(accountId, goodsId);
			build.setBoxSecondList(boxSecondListCache);
		}
		TblAssetAccount myAccount = tblAssetAccountService.account(assetId, Longs.tryParse(uid));
		if (Objects.nonNull(myAccount)) {
			AssetAccountResponse copyObj = CopyUtils.copyObj(myAccount, AssetAccountResponse.class);
			build.setMyAccount(copyObj);
		}
		if (Objects.nonNull(accountInfo) && Objects.equals(accountInfo.getOwnerId(), Longs.tryParse(uid))) {

		}
		AssetNoInfoRequest assetNoInfoRequest = new AssetNoInfoRequest();
		assetNoInfoRequest.setAssetId(String.valueOf(assetId));
		assetNoInfoRequest.setOwnerId(uid);
//		List<TblAssetNoDto> nftNoInfo = iTblAssetNoService.getNFTNoInfo(assetNoInfoRequest);
//		if(CollectionUtil.isNotEmpty(nftNoInfo)) {
//			build.setAssetNoList(nftNoInfo);
//		}
		return build;
	}

	private Boolean getChainOverDays(Long assetId, Long userId) {
		// 获取我的名下资产获取时间
		List<TblAssetNo> listByAssetId = iTblAssetNoService.getListByAssetId(assetId, userId, 1L);
		if (Objects.isNull(listByAssetId) || listByAssetId.isEmpty()) {
			return false;
		}
		TblAssetNo tblAssetNo = listByAssetId.get(0);
		return System.currentTimeMillis() - tblAssetNo.getReceiveTime() >= TimeUnit.MINUTES.toMillis(secondTypeShelfTimeMinutes);
	}
}